#!/usr/bin/env python


from Tkinter import Tk, Canvas, BOTH, LAST, FIRST


class Axis:

    def __init__(self,
                 mode,
                 min=0.0,
                 max=10.0,
                 tick=True,
                 tick_every=1,
                 tick_length = 2,
                 show_text = True,
                 end_type = LAST,
                 offsets=(0.1,0.1)):
        self.set_mode(mode)
        self.line = None
        self.min = 0.0
        self.max = 10.0
        self.tick = tick
        self.ticks = None
        self.tick_every = tick_every
        self.tick_length= tick_length
        self.show_text = show_text
        self.end_type = end_type
        self.text = None
        self.offsets = offsets

    def set_mode(self, mode):
        if mode in ['x', 'X']:
            self.mode = 1,0
        elif mode in ['y', 'Y']:
            self.mode = 0,1
        else:
            raise ValueError("Mode not supported!")

    def set_tick(self, tick):
        assert type(tick) == bool, "Value must be boolean."
        self.tick = tick
        self.draw(self.canvas, self.width, self.height)

    def draw_line(self, canvas, width, height):
        offset_x = self.offsets[0] * width
        offset_y = self.offsets[1] * height
        self.start_x = offset_x
        self.start_y = height - offset_y
        self.stop_x = self.start_x + \
                      self.mode[0] * (width - 2*offset_x)
        self.stop_y = self.start_y - \
                      self.mode[1] * (height - 2*offset_y)
        self.coords = self.start_x, self.start_y, \
                      self.stop_x, self.stop_y
        if self.line:
            canvas.coords(self.line, *self.coords)
        else:
            self.line = canvas.create_line(self.coords, arrow=self.end_type,
                                           width=2.0)

    def draw_ticks(self, canvas, width, height):
        tick_length = self.mode[0] * (self.tick_length*height*0.01) or \
                      self.mode[1] * (self.tick_length*width*0.01)
        offset_x = self.offsets[0] * width
        offset_y = self.offsets[1] * height
        if self.ticks:
            canvas.delete(*list(self.ticks))
        if self.tick:
            number = float((self.max - self.min)/self.tick_every)
            interval = self.mode[0] * (width - 2*offset_x)/number or \
                       self.mode[1] * (height - 2*offset_y)/number
            self.ticks = set()
            tick_range = range(int(number))
            if self.end_type is None:
                tick_range.append(number)
            for t in tick_range:
                start_x = self.mode[0] * (t * interval + offset_x) or \
                          self.mode[1] * (offset_x - tick_length)
                start_y = self.mode[0] * (height - offset_y) or \
                          self.mode[1] * (height - offset_y - t * interval)
                stop_x = start_x + self.mode[1] * tick_length
                stop_y = start_y + self.mode[0] * tick_length
                coords = start_x, start_y, stop_x, stop_y
                self.ticks.add(canvas.create_line(coords))

    def draw_text(self, canvas, width, height):
        text_offset = 0.05 * (self.mode[0] * height or \
                               self.mode[1] * width) 
        if self.text:
            canvas.delete(*list(self.text))
        if self.show_text:
            self.text = set()
            self.text.add(
                canvas.create_text(
                self.start_x - self.mode[1] * text_offset,
                self.start_y + self.mode[0] * text_offset,
                text=str(self.min)))
            self.text.add(
                canvas.create_text(
                self.stop_x - self.mode[1] * text_offset,
                self.stop_y + self.mode[0] * text_offset,
                text=str(self.max)))

    def draw(self, canvas, width, height):      
        self.canvas = canvas
        self.width = width
        self.height = height
        self.draw_line(canvas, width, height)
        self.draw_ticks(canvas, width, height)
        self.draw_text(canvas, width, height)


class Line:

    def __init__(self, y, x=None):
        self.tag = None
        if x:
            assert len(x)==len(y), "Vectors must have equal lengths!"
            self.y = y
            self.x = x
        else:
            self.y = y
            self.x = range(len(y))


class Plot(Canvas):

    def __init__(self,
                 *args,
                 **kwargs):

        self.issetup = False
        self.root = None
        self.height = 400
        self.width = 600
        self.border = 0.025
        self.tick = True
        self.background = "#ffffff"
        self.bg_box = None

        self.lines = set()
        self.add_lines(*args)

        for k, v in kwargs.iteritems():
            self.__dict__[k] = v

        if not self.root:
            self.root = Tk()
            
        Canvas.__init__(self, self.root,
                        width=self.width,
                        height=self.height)

        self.xaxis = Axis('x', tick=self.tick, end_type=self.end_type)
        self.yaxis = Axis('y', tick=self.tick, end_type=self.end_type)

        self.bind("<Configure>", self.redraw)
        self.issetup = True

    def add_lines(self, *lines):
        for line in lines:
            line.tag = None
            assert isinstance(line, Line), "Arguments must all be Lines!"
            self.lines.add(line)
        if self.issetup: self.draw_lines(self.width, self.height)

    def remove_lines(self, *lines):
        for line in lines:
            if line in self.lines:
                self.lines.remove(line)
                self.delete(line.tag)
        if self.issetup: self.draw_lines(self.width, self.height)

    def draw_background(self, color, width, height):
        start_x = self.border * width
        start_y = self.border * height
        stop_x = (1 - self.border) * width
        stop_y = (1 - self.border) * height
        coords = start_x, start_y, stop_x, stop_y
        if self.bg_box:
            self.coords(self.bg_box, coords)
            self.itemconfig(self.bg_box, fill=color, width=3)
        else:
            self.bg_box = self.create_rectangle(coords, fill=color, width=3)

    def draw_lines(self, width, height):
        start_x = self.xaxis.start_x
        start_y = self.xaxis.start_y
        for line in self.lines:
            points = []
            for i in range(len(line.y)):
                y = start_y - line.y[i] * height
                x = start_x + line.x[i] * width
                points.extend([x, y])
            if line.tag:
                self.coords(line.tag, *points)
            else:
                self.lines.remove(line)
                line.tag = self.create_line(points)
                self.lines.add(line)

    def draw(self):
        width = self.width
        height = self.height
        self.draw_background(self.background, width, height)
        self.xaxis.draw(self, width, height)
        self.yaxis.draw(self, width, height)
        self.draw_lines(width, height)

    def redraw(self, event):
        self.width = event.width
        self.height = event.height
        self.draw()


if __name__ == "__main__":
    l = Line([.1, .4, .7, .2, .0],
             [.1, .2, .3, .5, .7])
    p = Plot(l, end_type=None)
    p.place(relwidth=1,
            relheight=1,
            relx=0,
            rely=0)
